/*
 * SwingImage.java  0.4.0 / Oct 26, 2010
 *
 * Copyright (c) 2010 - 2011, cute4j.com and contributors.
 *
 * This software is licensed under the LGPL version 2.1 license.
 * A copy of this license is included in the file LICENSE-LGPL.txt.
 */
package com.cute4j.swing;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Arrays;

import com.cute4j.swing.graphics.Image;
import com.cute4j.swing.graphics.ImageConsumer;
import com.cute4j.swing.graphics.ImageProducer;
import com.cute4j.util.Utilities;

/**
 * Swing Image.
 *
 * @cute4j.native
 *    generate="header"
 *
 * @author Slobodan Vrkacevic
 * @version 0.4.0
 */
class SwingImage extends Image {

// Private-Private Static Fields. ----------------------------------------------

  private static Image emptyImage;

// Package-Private Fields. -----------------------------------------------------

  int imageReferences;
  SwingImageProducer imageProducer; // NOPMD
  byte[] data;
  int hashCode; // NOPMD

// Package-Private "Native" Fields. --------------------------------------------

  long _imagePointer;
  int _width;
  int _height;
  String _format;

// Package-Private Constructors. -----------------------------------------------

  SwingImage() {
    setImagePointer(_create()); // NOPMD
  }

  SwingImage(InputStream is) throws IOException {
    this(Utilities.toByteArray(is));
  }

  SwingImage(String fileName) throws IOException {
    this(new FileInputStream(fileName)); // NOPMD
  }

  SwingImage(File file) throws IOException {
    this(new FileInputStream(file));
  }

  SwingImage(URL location) throws IOException {
    this(location.openStream()); // NOPMD
  }

  SwingImage(byte[] imageData) throws IOException {
    this(imageData, imageData.length);
  }

  SwingImage(byte[] imageData, int length) throws IOException {
    if ((imageData == null) || (length > imageData.length)) {
      throw new IllegalArgumentException();
    }
    setImagePointer(_create(imageData, length)); // NOPMD
    if (_imagePointer == 0) {
      throw new IOException("SwingImage creation failed");
    }
    data = new byte[length];
    System.arraycopy(imageData, 0, data, 0, length);
    _format = getFormat(imageData);
  }

  SwingImage(long imagePointer, byte[] data, int width, int height) { // NOPMD
    _imagePointer = imagePointer;
    this.data = data;
    _width = width;
    _height = height;
    _format = getFormat(data);
  }

// Overridden Methods. ---------------------------------------------------------

  /**
   * {@inheritDoc}
   */
  public boolean equals(Object obj) {
    if (obj == this) {
      return true;
    }
    if ((obj != null) && (obj.getClass() == SwingImage.class)) {
      SwingImage image = (SwingImage)obj;
      return Arrays.equals(image.data, data);
    }
    return false;
  }

  /**
   * {@inheritDoc}
   */
  public int hashCode() {
    if (hashCode == 0) {
      int hashCode = 419;
      byte[] data = this.data;
      for (int i = data.length; --i >= 0;) {
        hashCode = 67 * hashCode + data[i];
      }
      this.hashCode = hashCode;
    }
    return hashCode;
  }

  /**
   * {@inheritDoc}
   */
  public int getWidth() {
    return _width;
  }

  /**
   * {@inheritDoc}
   */
  public int getHeight() {
    return _height;
  }

  /**
   * {@inheritDoc}
   */
  public ImageProducer getSource() {
    synchronized (data) {
      if (imageProducer == null) {
        imageProducer = new SwingImageProducer();
      }
      return imageProducer;
    }
  }

  /**
   * {@inheritDoc}
   */
  public Image getScaledInstance(int width, int height, int hints) {
    if ((hints != SCALE_FAST) && (hints != SCALE_SMOOTH)) {
      throw new IllegalArgumentException("Invalid hints");
    }
    long imagePointer = acquireImagePointer();
    Image scaledImage = _createScaled(imagePointer, width, height, hints,
                                      _format);
    releaseImagePointer();
    return scaledImage;
  }

  /**
   * {@inheritDoc}
   */
  public void flush() {
    synchronized (data) {
      if ((_imagePointer != 0) && (imageReferences == 0)) {
        _destroy(_imagePointer);
        _imagePointer = 0;
      }
    }
  }

  /**
   * {@inheritDoc}
   */
  protected void finalize() {
    synchronized (data) { // NOPMD
      if (_imagePointer != 0) {
        _destroy(_imagePointer);
        _imagePointer = 0;
      }
    }
  }

// Package-Private Methods. ----------------------------------------------------

  long acquireImagePointer() {
    synchronized (data) {
      imageReferences++;
      if (_imagePointer == 0) {
        setImagePointer(_create(data, data.length));
      }
      return _imagePointer;
    }
  }

  void releaseImagePointer() {
    synchronized (data) {
      imageReferences--;
      if ((imageReferences == 0) && (_imagePointer != 0)) {
        _destroy(_imagePointer);
        _imagePointer = 0;
      }
    }
  }

  long getImagePointer() {
    synchronized (data) {
      if (_imagePointer == 0) {
        setImagePointer(_create(data, data.length));
      }
      return _imagePointer;
    }
  }

  void setImagePointer(long imagePointer) {
    if (imagePointer == 0) {
      throw new OutOfMemoryError("SwingImage creation failed");
    }
    _imagePointer = imagePointer;
  }

// Package-Private Static Methods. ---------------------------------------------

  static String getFormat(byte[] imageData) {
    // simple image format detection
    if ((imageData != null) && (imageData.length > 3)) {
      if ((imageData[1] == 0x50) && (imageData[2] == 0x4E) &&
          (imageData[3] == 0x47)) {
        return "PNG";
      }
      if ((imageData[0] == (byte)0xFF) && (imageData[1] == (byte)0xD8)) {
        return "JPG";
      }
    }
    return "BMP";
  }

  static Image getEmptyImage() {
    if (emptyImage == null) { // NOPMD - thread safe singleton
      emptyImage = new SwingEmptyImage();
    }
    return emptyImage;
  }

// Package-Private Native Methods. ---------------------------------------------

  native long _create();

  native long _create(byte[] imageData, int lenght);

  native Image _createScaled(long imagePointer, int width, int height,
                             int hints, String format);

  native void _destroy(long imagePointer);

  /*
   * TODO:  add native method byte[] _getData(long imagePointer), and remove
   *        caching of large images
   */

// Inner Classes. --------------------------------------------------------------

  /**
   * Swing Image Producer.
   */
  final class SwingImageProducer implements ImageProducer {
    public void startProduction(ImageConsumer ic) {
      ic.setPixels(data, data.length);
    }
  }

  /**
   * Swing Image Consumer.
   */
  static final class SwingImageConsumer implements ImageConsumer {
    byte[] pixels;
    int length;
    public void setPixels(byte[] pixels, int length) { // NOPMD
      this.pixels = pixels;
      this.length = length;
    }
  }

  /**
   * Empty Image Class.
   */
  static final class SwingEmptyImage extends SwingImage {
    public boolean equals(Object obj) {
      return (obj == this);
    }
    public int hashCode() {
      return (int)_imagePointer;
    }
    public int getWidth() {
      return 0;
    }
    public int getHeight() {
      return 0;
    }
    public Image getScaledInstance(int width, int height, int hints) {
      return this;
    }
    public void flush() {
      // empty method
    }
    protected void finalize() {
      if (_imagePointer != 0) { // NOPMD
        _destroy(_imagePointer);
      }
    }
    long acquireImagePointer() {
      return _imagePointer;
    }
    long getImagePointer() {
      return _imagePointer;
    }
    void releaseImagePointer() {
      // empty method
    }
  }

}
